import { ref, watch } from 'vue';
import { RectBoxProps } from './use-rect-box.js';
import { addOrDel } from '@/util/index.js';
import { UUID } from '@/util/rnd.js';
import { DirSign, assignId } from '../common.js';

export const Layer = startPoint => Object.assign(
  { label: [], color: 'transparent' },
  RectBoxProps(),
  startPoint
);

export function useLayer(xs, lastSelectIndex) {
  /** @type {import('vue').Ref<'switch' | 'edit'>} */
  const arrowAction = ref('switch');
  
  /** 编辑标签中的 @type {import('vue').Ref<null | ObjDetn.Layer>} */
  const editingDataItem = ref(null);

  /** 活跃点或矩形拖拽在中的 @type {import('vue').Ref<null | ObjDetn.Layer>} */
  const activeDataItem = ref(null);
  
  const selectedDataItems = ref(new Set());
  const copiedDataItems = ref([]);
  const autoAppendDataItemsExpected = ref(new Set());

  /**
   * 选择数据项，更新已选数据列表和最近一次选择列表项下标
   * @param {'single' | 'multiple'} mode 单个或多个
   * @param {'switch' | 'edit'} action switch=数据列表切换选择 edit=与舞台元素交互
   * @param {ObjDetn.RectBoxProps | 'all'} v 数据项或 'all' 全选
   */
  function select(mode, action, v) {
    arrowAction.value = action;

    // 像资源管理器一样
    // 全选就选所有项，不更新上次选择项下标
    if ('all' == v) {
      selectedDataItems.value.clear();
      xs.value.forEach(x => selectedDataItems.value.add(x));
      return;
    }

    // 单选和多选会更新上次选择项下标
    lastSelectIndex.value = xs.value.indexOf(v);

    // 按 ctrl 是选择或取消选择
    if ('multiple' == mode) {
      addOrDel(v, selectedDataItems.value);
      return;
    }

    // 单选
    selectedDataItems.value.clear();
    v && selectedDataItems.value.add(v);
  };
  
  /** @param {'prev' | 'next'} dir */
  function findSibling(dir) {
    return xs.value[ lastSelectIndex.value + DirSign(dir) ];
  }
  
  function clear() {
    autoAppendDataItemsExpected.value.clear();
    selectedDataItems.value.clear();
    xs.value.splice(0);
  }

  function del(dataItem) {
    if (!xs.value.includes(dataItem)) { return; }
    xs.value.splice( xs.value.indexOf(dataItem), 1 );
    setExpectedAutoAppend(dataItem, 'delete');
  }
  
  function toggleVisible() {
    // 只要有显示的就都隐藏，否则都显示
    const visible = R.compose( R.not, R.any(R.prop('visible')) )(xs.value);
    xs.value.forEach(x => x.visible = visible);
  }

  const clone = dataItem => R.mergeDeepRight(dataItem, { id: UUID() });

  function copy() {
    copiedDataItems.value = [...Array.from(selectedDataItems.value)];
    localStorage.setItem('copiedDataItems', JSON.stringify(copiedDataItems.value)); // 用于跨包复制
  }
  
  function paste(f) {
    xs.value.push( ...R.compose( f, R.map(clone) )(copiedDataItems.value) );
  }

  /**
   * @param {'all' | ObjDetn.Layer} v
   * @param {'add' | 'delete'} force
   */
  function setExpectedAutoAppend(v, force) {
    if ('all' == v) {
      autoAppendDataItemsExpected.value.size < xs.value.length
      ? xs.value.forEach(x => autoAppendDataItemsExpected.value.add(x))
      : autoAppendDataItemsExpected.value.clear();
      return;
    }

    addOrDel(v, autoAppendDataItemsExpected.value, force);
  }

  /** @param {{ x: number; y: number; }} move  */
  function kbdMove(move) {
    if ( !('edit' == arrowAction.value && activeDataItem.value) ) { return; }

    const calcMovedPoint = R.compose( R.mapObjIndexed((v, k) => v + move[k]), R.pick(['x', 'y']) );
    Object.assign(activeDataItem.value, calcMovedPoint(activeDataItem.value));
    return R.pick(['x', 'y'], activeDataItem.value);
  };

  watch(
    activeDataItem,
    dataItem => {
      /** 若数据非 `null` 且无 `id` 则视作刚创建的 */
      const isNewlyCreated = R.both( R.isNotNil, R.complement(R.prop('id')) );
      R.both(isNewlyCreated, assignId)(dataItem);
      select('single', dataItem ? 'edit' : 'switch', dataItem); // 活跃框值不管是否为 null 都要调 selectToEdit 更新相关数据
    },
  );

  return {
    arrowAction,
    editingDataItem,
    activeDataItem,
    selectedDataItems,
    copiedDataItems,
    autoAppendDataItemsExpected,
    clear,
    del,
    toggleVisible,
    select,
    findSibling,
    clone,
    copy,
    paste,
    setExpectedAutoAppend,
    kbdMove,
  };
}

export function existIn(xs) {
  return autoLayer => {
    /** 两数相减的差若小于指定值则视为相似 */
    const isSimilar = (diff, n1, n2) => Math.abs(n1 - n2) <= diff;
  
    return R.any(
      R.allPass([
        dataItem => R.equals(autoLayer.label, dataItem.label),
        dataItem => R.all( prop => isSimilar(1e-4, autoLayer[prop], dataItem[prop] ), ['x', 'y', 'width', 'height'] ),
      ]),
      xs.value
    );
  };
}
